# Wei Jiang
# example For Building Interface Between Other Languages And CPlusPlus.py

from ctypes import *

# single query result 
class QUERY_RESULT(Structure):
    _fields_ = [("score",c_double),
                ("docid",c_ulong),
                ("url",c_char_p)]
    
# single posting result 
class POSTING_RESULT(Structure):
    _fields_ = [("queryTerm",c_char_p),
                ("queryTermLength",c_uint),
                ("queryTermID",c_uint),
                ("docid",c_ulong),
                ("score",c_double)]
    


# proxy query processor
class Foo():

    def __init__(self,indexFileDirectory,defaultIndexPrefixFileName):
        # the lib we are going to load.
        self.lib = CDLL('./irtk.so')
        self.foo = self.lib.Foo_new(indexFileDirectory,defaultIndexPrefixFileName)

    def __del__(self):
        self.lib.Foo_delete(self.foo)
        
    def set_index_path(self,indexFileDirectory):
        self.lib.Foo_set_index_path(self.foo,indexFileDirectory)
    
    def set_index_prefix_name(self,defaultIndexPrefixFileName):
        self.lib.Foo_set_index_prefix_name(self.foo,defaultIndexPrefixFileName)
    
    # push Query In Order To Get Results. Push a query into the local query processor for query processing, when the set of results returned, the callback1 function will be called in the python code to deal with the return result set.
    def pushQueryInOrderToGetResults(self, queryId, channel, query, top_k):
        self.lib.Foo_push(self.foo, queryId, channel, query, top_k, CMPFUNC1(self.callback))
        
    # push Query In Order To Modify Precomputed Scores. Push a query into the local query processor for query processing, when the set of results returned, the callback1 function will be called in the python code to deal with the return result set.
    def pushQueryInOrderToModifyPrecomputedScores(self, queryId, query, semantics, mode):
        if mode == "Interactive":
            self.lib.Foo_testPush(self.foo, queryId, query, semantics, mode, CMPFUNC2(self.callback2))
        elif mode == "Programmatic":
            self.lib.Foo_testPush(self.foo, queryId, query, semantics, mode, CMPFUNC2(self.callback2))
        else:
            print "Unsupported Mode Detected."
      
    def computeTopKResultsSet(self,Posting_Results):
        print "computeTopKResultsSet(...)"
        # read the original scores.
        pythonResultsCounter = 0
        
        currentDocid = "-1"
        score = 0
        newDocumentCandidateList = []
        for index,item in enumerate(Posting_Results):
            print "index:",index
            # list is null terminated
            if item.queryTerm is None:
                break
            
            if currentDocid != str(item.docid):
                newDocumentCandidateTuple = (score,currentDocid)
                newDocumentCandidateList.append(newDocumentCandidateTuple)
                newDocumentCandidateList.sort(cmp=None, key=None, reverse=True)
                
                currentDocid = str(item.docid)
                score = 0
                score += item.score
            else:
                score += item.score
            
            
            
            '''
            str(item.queryTermID) 
            str(item.queryTermLength) 
            str(item.docid) 
            "{0:.6f}".format( float(item.score))
            '''
            
            pythonResultsCounter += 1
            
        print "pythonResultsCounter:",pythonResultsCounter
        print "len(newDocumentCandidateList):",len(newDocumentCandidateList)
        for resultTuple in newDocumentCandidateList[0:20]:
            print resultTuple
        
    def modifyPreComputedScores(self,Posting_Results):
        # <class '__main__.LP_POSTING_RESULT'>
        print "modifyPreComputedScores(...)"
        
        # read the original scores.
        for index,item in enumerate(Posting_Results):
            # list is null terminated
            if item.queryTerm is None:
                break
            outputLine = "(" + str(item.queryTermID) + "," + str(item.queryTermLength) + "," + str(item.docid) + "," + "{0:.6f}".format( float(item.score)) + ")" 
            # for debug
            # print index,outputLine
        
        
        # modify the scores.
        # suppose I want to modify 2 postings for apple and banana with the docID:40067
        # In python, I first create a list.
        modifiedScorePostings = []
        # create a new posting tuple in python and append to the python list.
        newModifiedPostingScore1Tuple = ("apple","45408","101.1")
        newModifiedPostingScore2Tuple = ("banana","45408","102.2")
        
        newModifiedPostingScore3Tuple = ("apple","25050200","103.3")
        newModifiedPostingScore4Tuple = ("banana","25050200","104.4")
        
        newModifiedPostingScore5Tuple = ("apple","24547697","105.5")
        newModifiedPostingScore6Tuple = ("banana","24547697","106.6")
        
        newModifiedPostingScore7Tuple = ("apple","22001948","107.7")
        newModifiedPostingScore8Tuple = ("banana","22001948","108.8")
        
        modifiedScorePostings.append(newModifiedPostingScore1Tuple)
        modifiedScorePostings.append(newModifiedPostingScore2Tuple)

        modifiedScorePostings.append(newModifiedPostingScore3Tuple)
        modifiedScorePostings.append(newModifiedPostingScore4Tuple)
        
        modifiedScorePostings.append(newModifiedPostingScore5Tuple)
        modifiedScorePostings.append(newModifiedPostingScore6Tuple)
        
        modifiedScorePostings.append(newModifiedPostingScore7Tuple)
        modifiedScorePostings.append(newModifiedPostingScore8Tuple)
               
        #call the corresponding python,c++ function to modify the scores.
        for currentModifiedPostingScoreTuple in modifiedScorePostings:
            (currentQueryTerm,currentDocID,currentPostingScoreInStringFormat) = currentModifiedPostingScoreTuple
            #print "type:",type(currentQueryTerm)
            #print "type:",type(currentDocID)
            #print "type:",type(currentPostingScoreInStringFormat)
            self.lib.Foo_update_inverted_indexes_precomputed_scores(self.foo,currentQueryTerm,currentDocID,currentPostingScoreInStringFormat)
        
        
    def report(self):
        self.lib.Foo_report(self.foo)
    # self.callback prototype for results
    #   returns int 0 = done with results (mem free() on the CPP side)
    
    
    def callback2(self, qid, numResults, results_ptr):
        print "in python callback2 function"
        if qid != -1:
            print "Got", numResults, "involved postings for queryID:",qid
            Posting_Results = cast(results_ptr,POINTER(POSTING_RESULT))
            if qid == 1:
                print "called 1"
                self.Posting_Results_External1 = Posting_Results
            elif qid == 2:
                print "called 2"
                self.Posting_Results_External2 = Posting_Results
            
            
            # read the original scores.
            pythonResultsCounter = 0
            for index,item in enumerate(Posting_Results):
                # list is null terminated
                if item.queryTerm is None:
                    break
                outputLine = "(" + str(item.queryTermID) + "," + str(item.queryTermLength) + "," + str(item.docid) + "," + "{0:.6f}".format( float(item.score)) + ")" 
                pythonResultsCounter += 1
                #print index,outputLine
            print "pythonResultsCounter:",pythonResultsCounter
            
            # This is the way of how to modifyPreComputedScores ...
            # self.modifyPreComputedScores(Posting_Results)
            
            # This is the way of how to compute the top-k result set...
            # self.computeTopKResultsSet(Posting_Results)
                        
        else:
            pass
        return 0


    # self.callback prototype for results
    #   returns int 0 = done with results (mem free() on the CPP side)
    def callback(self, qid, channel, numResults, results_ptr):
        print "in python callback function"
        if qid != -1:
            print "Got", numResults, "results for (queryID,channelID):", "(",qid,",",channel,")"
            Query_Results = cast(results_ptr,POINTER(QUERY_RESULT))
            topReturnDocIDForQuery = []
            #For each query, will have a new tuple.
            for item in Query_Results:
                # list is null terminated
                if item.url is None:
                    break
                # do something with the results
                
                #current version
                outputLine = "Score: " + "{0:.6f}".format( float(item.score)) + "    "+ "DocID: " + str(item.docid) +"    "+ "URL: " + item.url
                print outputLine
                
                # currently, there are no url involved. 
                # I will have to fix that in a later case.
                
                topReturnDocIDForQuery.append( (int(item.docid), float(item.score) ,str(item.url) ))
                #topReturnDocIDForQuery.append( int(item.docid) )
            newTuple = (qid,topReturnDocIDForQuery)
        else:
            pass
        return 0
    
print "Updated by Wei: 2012/08/08"
print "Program begins."
CMPFUNC1 = CFUNCTYPE(c_int,c_int, c_int, c_int, POINTER(QUERY_RESULT))

CMPFUNC2 = CFUNCTYPE(c_int,c_int,c_int, POINTER(POSTING_RESULT))

defaultIndexFileDirectory = "/data5/team/weijiang/compatibleIndexesWithIRTK/gov2"
hintMessage1 = "Please enter the directory where the index files locate[The default path:" + defaultIndexFileDirectory + "]:"
indexFileDirectory = raw_input(hintMessage1)

defaultIndexPrefixFileName = "completedQueryTermsForGOV2EfficiencyTaskWithWrongScoresStored4Part1"
hintMessage2 = "Please enter the index prefix name[The default name:" + defaultIndexPrefixFileName + "]:"
indexFilePrefixName = raw_input(hintMessage2)

if indexFileDirectory == "":
    indexFileDirectory = defaultIndexFileDirectory
else:
    pass

if indexFilePrefixName == "":
    indexFilePrefixName = defaultIndexPrefixFileName
else:
    pass

print "indexFileDirectory:",indexFileDirectory
print "indexFilePrefixName:",indexFilePrefixName

localSearchEngineBlackBox = Foo(indexFileDirectory,indexFilePrefixName)

# This is the new example of how to modify the scores stored in the inverted index.Updated by Wei, 2012/08/02
# localSearchEngineBlackBox.pushQueryInOrderToModifyPrecomputedScores(1,"100hr a","OR")
# localSearchEngineBlackBox.pushQueryInOrderToModifyPrecomputedScores(1,"100hr 1031b","OR")

# updated by Wei: 2012/08/09
# For your interaction
# localSearchEngineBlackBox.pushQueryInOrderToModifyPrecomputedScores(1,"apple banana","AND","Interactive")

# For your programs
# thread1:
localSearchEngineBlackBox.pushQueryInOrderToModifyPrecomputedScores(1,"apple","AND","Programmatic")

'''
# thread2:
# localSearchEngineBlackBox.pushQueryInOrderToModifyPrecomputedScores(2,"apple banana","OR","Programmatic")

print "localSearchEngineBlackBox.Posting_Results_External1:",localSearchEngineBlackBox.Posting_Results_External1

# read the original scores.
tempCounter1 = 0
for index,item in enumerate(localSearchEngineBlackBox.Posting_Results_External1):
    # list is null terminated
    if item.queryTerm is None:
        break
    outputLine = "(" + str(item.queryTermID) + "," + str(item.queryTermLength) + "," + str(item.docid) + "," + "{0:.6f}".format( float(item.score)) + ")" 
    tempCounter1 += 1
    # for debug
    # print index,outputLine
print "tempCounter1:",tempCounter1

print "localSearchEngineBlackBox.Posting_Results_External2:",localSearchEngineBlackBox.Posting_Results_External2

# read the original scores.
tempCounter2 = 0
for index,item in enumerate(localSearchEngineBlackBox.Posting_Results_External2):
    # list is null terminated
    if item.queryTerm is None:
        break
    outputLine = "(" + str(item.queryTermID) + "," + str(item.queryTermLength) + "," + str(item.docid) + "," + "{0:.6f}".format( float(item.score)) + ")" 
    tempCounter2 += 1
    # for debug
    # print index,outputLine
print "tempCounter2:",tempCounter2
'''

print "Program end."
